package com.hspedu.hspspringmvc.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hspedu.hspspringmvc.annotation.Controller;
import com.hspedu.hspspringmvc.annotation.RequestMapping;
import com.hspedu.hspspringmvc.annotation.RequestParam;
import com.hspedu.hspspringmvc.annotation.ResponseBody;
import com.hspedu.hspspringmvc.context.HspWebApplicationContext;
import com.hspedu.hspspringmvc.handler.HspHandler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;



/**
 * 1. HspDispatcherServlet充当原生DispatcherServlet
 * 2. 本质是一个Servlet, 继承HttpServlet
 */
public class HspDispatcherServlet extends HttpServlet {

    //定义属性 handlerList , 保存HspHandler[url和控制器方法的映射]
    private List<HspHandler> handlerList =
            new ArrayList<>();
    //定义属性 hspWebApplicationContext,自己的spring容器
    HspWebApplicationContext hspWebApplicationContext = null;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

        String configLocation =
                servletConfig.getInitParameter("contextConfigLocation");

        //创建spring容器
        hspWebApplicationContext =
                new HspWebApplicationContext(configLocation);

        hspWebApplicationContext.init();
        //调用 initHandlerMapping ， 完成url和控制器方法的映射
        initHandlerMapping();
        //输出handlerList
        System.out.println("handlerList初始化的结果= " + handlerList);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //System.out.println("--HspDispatcherServlet--doPost---");
        //调用方法，完成分发请求
        executeDispatch(req, resp);
    }

    //编写方法，完成url 和 控制器方法的映射
    private void initHandlerMapping() {
        if (hspWebApplicationContext.ioc.isEmpty()) {
            //判断当前的ioc容器是否为null
            return;
        }

        //遍历ioc容器的bean对象,然后进行url映射处理
        //java基础 map的遍历
        for (Map.Entry<String, Object> entry : hspWebApplicationContext.ioc.entrySet()) {
            //先取出注入的Object的clazz对象
            Class<?> clazz = entry.getValue().getClass();
            //如果注入的Bean是Controller
            if (clazz.isAnnotationPresent(Controller.class)) {
                //取出它的所有方法
                Method[] declaredMethods = clazz.getDeclaredMethods();
                //遍历方法
                for (Method declaredMethod : declaredMethods) {
                    //判断该方法是否有@RequestMapping
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        //取出@RequestMapping值->就是映射路径
                        RequestMapping requestMappingAnnotation =
                                declaredMethod.getAnnotation(RequestMapping.class);
                        //这里小伙伴可以把工程路径+url
                        //getServletContext().getContextPath()
                        // /springmvc/monster/list
                        String url = requestMappingAnnotation.value();
                        //创建HspHandler对象->就是一个映射关系
                        HspHandler hspHandler = new HspHandler(url, entry.getValue(), declaredMethod);
                        //放入到handlerList
                        handlerList.add(hspHandler);
                    }
                }
            }
        }
    }


    //编写方法，通过request对象，返回HspHandler对象
    //如果没有，就返回null
    private HspHandler getHspHandler(HttpServletRequest request) {
        //1.先获取的用户请求的uri 比如http://localhost:8080/springmvc/monster/list
        //  uri = /springmvc/monster/list
        //2. 这里小伙伴要注意得到uri 和 保存url 是有一个工程路径的问题
        // 两个方案解决 =>第一个方案: 简单 tomcat 直接配置 application context =>/
        // 第二个方案 保存 hsphandler对象 url 拼接 getServletContext().getContextPath()
        String requestURI = request.getRequestURI();
        //遍历handlerList
        for (HspHandler hspHandler : handlerList) {
            if (requestURI.equals(hspHandler.getUrl())) {//说明匹配成功
                return hspHandler;
            }
        }
        return null;
    }



    //编写方法，完成分发请求任务
    private void executeDispatch(HttpServletRequest request,
                                 HttpServletResponse response) {

        HspHandler hspHandler = getHspHandler(request);
        try {
            if (null == hspHandler) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//匹配成功, 反射调用控制器的方法

                //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
                //1. 得到目标方法的所有形参参数信息[对应的数组]
                Class<?>[] parameterTypes =
                        hspHandler.getMethod().getParameterTypes();

                //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时，会使用到
                Object[] params =
                        new Object[parameterTypes.length];

                //3遍历parameterTypes形参数组,根据形参数组信息，将实参填充到实参数组

                for (int i = 0; i < parameterTypes.length; i++) {
                    //取出每一个形参类型
                    Class<?> parameterType = parameterTypes[i];
                    //如果这个形参是HttpServletRequest, 将request填充到params
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = request;
                    } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                        params[i] = response;
                    }
                }

                //处理提交的数据中文乱码
                request.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap =
                        request.getParameterMap();

                //2. 遍历parameterMap 将请求参数，按照顺序填充到实参数组params
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                    //取出key，这name就是对应请求的参数名
                    String name = entry.getKey();

                    String value = entry.getValue()[0];

                    //专门编写一个方法，得到请求的参数对应的是第几个形参
                    int indexRequestParameterIndex =
                            getIndexRequestParameterIndex(hspHandler.getMethod(), name);
                    if (indexRequestParameterIndex != -1) {//找到对应的位置
                        params[indexRequestParameterIndex] = value;
                    } else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]

                        List<String> parameterNames =
                                getParameterNames(hspHandler.getMethod());
                        for (int i = 0; i < parameterNames.size(); i++) {
                            //如果请求参数名和目标方法的形参名一样，说明匹配成功
                            if (name.equals(parameterNames.get(i))) {
                                params[i] = value;//填充到实参数组
                                break;
                            }
                        }
                    }
                }


                //反射调用目标方法
                Object result = hspHandler.getMethod()
                        .invoke(hspHandler.getController(), params);

                //这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
                if (result instanceof String) {

                    String viewName = (String) result;
                    if(viewName.contains(":")){//返回的String 结果forward:/login_ok.jsp 或者 redirect:/xxx/xx/xx.xx
                        String viewType = viewName.split(":")[0];//forward | redirect
                        String viewPage = viewName.split(":")[1];//要跳转的页面名
                        //判断是forward 还是 redirect
                        if("forward".equals(viewType)) {//说明你希望请求转发
                            request.getRequestDispatcher(viewPage)
                                    .forward(request,response);
                        } else if("redirect".equals(viewType)) {//说明你希望重定向
                            response.sendRedirect(viewPage);
                        }
                    } else {//默认是请求转发
                        request.getRequestDispatcher(viewName)
                                .forward(request,response);
                    }

                }//这里还可以扩展
                else if(result instanceof ArrayList) {//如果是ArrayList

                    //判断目标方法是否有@ResponseBody
                    Method method = hspHandler.getMethod();
                    if(method.isAnnotationPresent(ResponseBody.class)) {


                        ObjectMapper objectMapper = new ObjectMapper();
                        String resultJson =
                                objectMapper.writeValueAsString(result);

                        response.setContentType("text/html;charset=utf-8");

                        PrintWriter writer = response.getWriter();
                        writer.write(resultJson);
                        writer.flush();
                        writer.close();

                    }

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public int getIndexRequestParameterIndex(Method method, String name) {

        //1.得到method的所有形参参数
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //取出当前的形参参数
            Parameter parameter = parameters[i];
            //判断parameter是不是有@RequestParam注解
            boolean annotationPresent = parameter.isAnnotationPresent(RequestParam.class);
            if (annotationPresent) {//说明有@RequestParam
                //取出当前这个参数的 @RequestParam(value = "xxx")
                RequestParam requestParamAnnotation =
                        parameter.getAnnotation(RequestParam.class);
                String value = requestParamAnnotation.value();
                //这里就是匹配的比较
                if (name.equals(value)) {
                    return i;//找到请求的参数，对应的目标方法的形参的位置
                }
            }
        }
        //如果没有匹配成功，就返回-1
        return -1;
    }




    public List<String> getParameterNames(Method method) {

        List<String> parametersList = new ArrayList<>();
        //获取到所以的参数名->这里有一个小细节
        //在默认情况下 parameter.getName() 得到的名字不是形参真正名字
        //而是 [arg0, arg1, arg2...], 这里我们要引入一个插件，使用java8特性，这样才能解决
        Parameter[] parameters = method.getParameters();
        //遍历parameters 取出名称，放入parametersList
        for (Parameter parameter : parameters) {
            parametersList.add(parameter.getName());
        }
        System.out.println("目标方法的形参列表=" + parametersList);
        return parametersList;
    }
}
